component accessors="true" displayname="EmailValidator" hint="Validates an e-mail address based on its format and against the SMTP server" {

	/* Public members */
	property string email;
	property string domain;

	/* Private members */
	variables.email = "";
	variables.domain = "";
	variables.oIDirCtx = createObject("java", "javax.naming.directory.InitialDirContext");

	/* Default constructor */
	public any function init (required string email) {
		if (!isValid("email", arguments.email)) {
			throw "Invalid e-mail format";
		}

		setEmail(arguments.email);
		setDomain(listRest(arguments.email, "@"));
		return this;
	}

	/* Gets a list of available mail exchangers for this domain */
	public array function getMailServers () {
		var env = {"java.naming.factory.initial" = "com.sun.jndi.dns.DnsContextFactory"};
		var oDirCtx = variables.oIDirCtx.init(env);
		var oAttr = oDirCtx.getAttributes(getDomain(), ["MX"]).get("MX");

		// Check to see if MX records were available. If not, search for the A record
		if (!structKeyExists(local, "oAttr") || oAttr.size() EQ 0) {
			oAttr = oDirCtx.getAttributes(getDomain(), ["A"]).get("A");
			if (!structKeyExists(local, "oAttr")) {
				throw "No match for domain: #getDomain()#";
			}
		}

		// Create a list of the available servers
		var arServers = [];
		var enumServers = oAttr.getAll();

		while (enumServers.hasMore()) {
			var svr = enumServers.next();
			var idx = listLen(svr, " ") EQ 2 ? 2 : 1;
			svr = toString(listGetAt(svr, idx, " "));
			if (svr.endsWith(".")) {
				svr = left(svr, len(svr) - 1);
			}
			arrayAppend(arServers, svr);
		}

		return arServers;
	}

	/* Validates the e-mail address against all available mail exchangers
	 * for the supplied domain.
	 *
	 * NOTE: This method should not be considered to provide 100% accurate data.
	 * Some domains may not have available MX records, connection issues to the server,
	 * or may require SMTP authentication
	 */
	public boolean function isAddressValid () {

		var arServers = getMailServers();
		var valid = false;
		var res = "";

		if (arrayLen(arServers) EQ 0) {
			return false;
		}

		for (i = 1; i LT arrayLen(arServers); i++) {
			try {

				var oSoc = createObject("java", "java.net.Socket").init(arServers[i], 25);
				var oBR = createObject("java", "java.io.BufferedReader").init(createObject("java", "java.io.InputStreamReader").init(oSOC.getInputStream()));
				var oBW = createObject("java", "java.io.BufferedWriter").init(createObject("java", "java.io.OutputStreamWriter").init(oSOC.getOutputStream()));

				if (read(oBR) NEQ 220) {
					throw "Invalid header";
				}

				send(oBW, "EHLO validemail.com");
				if (read(oBR) NEQ 250) {
					throw "Not ESMTP";
				}

				send(oBW, "MAIL FROM: <is@validemail.com>");
				if (read(oBR) NEQ 250) {
					throw "Sender rejected";
				}

				send(oBW, "RCPT TO: <#getEmail()#>");
				if (read(oBR) NEQ 250) {
					throw "Address is not valid";
				}
				send(oBW, "RSET");
				send(oBW, "QUIT");

				valid = true;
			} catch (any ex) {}
			finally {
				try {oBR.close();} catch (any ex) {}
				try {oBW.close();} catch (any ex) {}
				try {oSoc.close();} catch (any ex) {}

				if (valid) {
					break;
				}
			}
		}
		return valid;
	}

	/* Sends a command to the socket */
	private void function send (required any bw, required string cmd) {
		bw.write("#cmd#");
		bw.write(chr(13));
		bw.write(chr(10));
		bw.flush();
	}

	/* Reads the response from the socket */
	private numeric function read (required any br) {
		var res = 0;
		var pfx = "";
		var line = "";

		do {
			line = br.readLine();
			if (structKeyExists(local, "line")) {
				pfx = line.substring(0, 3);
				try {
					res = val(pfx*1);
				} catch (any ex) {
					res = -1;
				}
				if (line.charAt(3) NEQ "-") {
					break;
				}
			}
		} while (structKeyExists(local, "line"));

		return res;
	}
}